如何使用 Create React App 和 Electron Builder 构建 Electron 应用

Electron-React

最近我决定做一个桌面应用来下载和保存我的 Google Photos。因为我曾被丢失所有照片折磨够了。虽然 Google 本身有一些选项可以配置实现,但是实践下来貌似都有问题。

如果你想使用这款应用,你可以在这里下载 OSX
或者 WIN

技术栈上我选择 Electron 和 React,因为这使得我的工作很有趣,而且最终效果不错.

在这篇博客中,我会分享一些配置和我踩过的坑。

我刚开始开发的时候,我参考了这两篇博客 1
2,感谢。

好的,结下来我们一起来学学如何构建一个使用 Create React App 开发的 Electron 应用,然后在使用 Electron Builder 进行打包分发。

我们先来了解一下整个技术栈然后再开始。如果你想跳过,可以直接看源码

技术栈

Electron 是一个使用 JavaScript, HTML, and CSS 等 Web 技术创建原生应用的框架。在我们的案例中,我们使用的是 React

React 是构建用户界面的 JavaScript 库……

为了让我们的 React 项目配置更容易,我们使用Create React App快速创建 React 项目。Create React
App (CRA)很棒,因为它节省了时间,并消除了配置难题。

Create React App 是一个可以让你在构建应用时获得先发优势的工具,由 Facebook 开发者创建。它节省了安装和配置的时间。你只需要简单地运行一个命令,Create React App 就会帮你安装好用于启动项目的工具

Rescripts 可以让你无需eject也可以自定义 CRA。

弹出(eject) CRA 是你应该避免的操作,因为这将意味着你再也不会受益于 CRA 的未来更新。

Electron Builder 用于打包我们的桌面应用进行分发。

项目搭建

使用 Create React App 快速创建项目

1
2
npx create-react-app my-app
cd my-app

安装 Electron

1
yarn add electron electron-builder --dev

和一些我们需要的工具

1
2
yarn add wait-on concurrently --dev
yarn add electron-is-dev

创建public/electron.js文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

const path = require("path");
const isDev = require("electron-is-dev");

let mainWindow;

function createWindow() {
mainWindow = new BrowserWindow({ width: 900, height: 680 });
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../build/index.html")}`
);
if (isDev) {
// Open the DevTools.
//BrowserWindow.addDevToolsExtension('<location to your react chrome extension>');
mainWindow.webContents.openDevTools();
}
mainWindow.on("closed", () => (mainWindow = null));
}

app.on("ready", createWindow);

app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});

app.on("activate", () => {
if (mainWindow === null) {
createWindow();
}
});

package.json文件的scripts 项添加如下命令:

1
"electron-dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\""

这个script将会在启动 Electron 之前 等待应用在localhost:3000运行起来。

package.json文件的main 项添加如下内容:

1
"main": "public/electron.js",

现在,你的 package.json应该是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"electron-is-dev": "^1.0.1",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-scripts": "2.1.5"
},
"main": "public/electron.js",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron-dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\""
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"],
"devDependencies": {
"concurrently": "^4.1.0",
"electron": "^4.0.6",
"electron-builder": "^20.38.5",
"wait-on": "^3.2.0"
}
}

这时,你可以通过以下命令以开发模式启动你的项目

1
yarn electron-dev

你应该会看到下图这样
electron-boilerplate
如果你看到了,恭喜你可以进行接下来的开发了。🤩

Now, if you need to access the fs module like I did, you’ll quickly hit the Module not found error.
See here. 如果你需要使用fs
模块,你可能很快就会遇到Module not found
这样的错误。参考

要解决这个问题,我们需要设置Webpacktargetelectron-renderer。但是哦我们不想弹出(eject) CRA
去做这件事。所以我们使用Rescripts。下面教你如何使用。

首先,安装Rescripts

1
yarn add @rescripts/cli @rescripts/rescript-env --dev

然后,将 package.json里面的scripts 项从

1
2
3
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",

改为

1
2
3
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",

现在新建 .rescriptsrc.js文件,内容如下:

1
module.exports = [require.resolve("./.webpack.config.js")];

最后新建.webpack.config.js,内容如下:

1
2
3
4
5
// define child rescript
module.exports = (config) => {
config.target = "electron-renderer";
return config;
};

现在就可以无需担心地使用fs 模块了

开始打包

非常棒,现在我们可以准备打包我们的项目了

首先,安装 Electron Builder 和 Typescript

1
yarn add electron-builder typescript --dev

默认情况下 CRA 会使用绝对路径构建修改index.html文件。这会使得 Electron 加载出错。这需要一些配置来修正。

package.json中的homepage项的值设为

1
"homepage": "./",

接下来我们添加给package.jsonscripts 项添加electron-pack命令用于打包构建(build)结果

1
2
3
"postinstall": "electron-builder install-app-deps",
"preelectron-pack": "yarn build",
"electron-pack": "build -mw"

"postinstall": "electron-builder install-app-deps" 将会确保你的原生依赖总是会匹配 electron 的版本

"preelectron-pack": "yarn build" 用于构建(build) CRA 项目

"electron-pack": "build -mw" 用于打包 Mac (m) Windows (w)应用

在我们可以运行这个命令之前在package.json里给 Electron Builder 配置如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"author": {
"name": "Your Name",
"email": "your.email@domain.com",
"url": "https://your-website.com"
},
"build": {
"appId": "com.my-website.my-app",
"productName": "MyApp",
"copyright": "Copyright © 2019 ${author}",
"mac": {
"category": "public.app-category.utilities"
},
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "assets"
}
}

你可以在在 这里查看在所有的 Electron Builder 配置项

你可能还会创建assets 文件夹以存放应用的图标(icon)。格式可以参考 这里

最终整个 package.json 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
"name": "my-app",
"description": "Electron + Create React App + Electron Builder",
"version": "0.1.0",
"private": true,
"author": {
"name": "Your Name",
"email": "your.email@domain.com",
"url": "https://your-website.com"
},
"build": {
"appId": "com.my-website.my-app",
"productName": "MyApp",
"copyright": "Copyright © 2019 ${author}",
"mac": {
"category": "public.app-category.utilities"
},
"files": ["build/**/*", "node_modules/**/*"],
"directories": {
"buildResources": "assets"
}
},
"dependencies": {
"electron-is-dev": "^1.0.1",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-scripts": "2.1.5"
},
"homepage": "./",
"main": "public/electron.js",
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "react-scripts eject",
"electron-dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\"",
"postinstall": "electron-builder install-app-deps",
"preelectron-pack": "yarn build",
"electron-pack": "build -mw"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"],
"devDependencies": {
"@rescripts/cli": "^0.0.10",
"@rescripts/rescript-env": "^0.0.5",
"concurrently": "^4.1.0",
"electron": "^4.0.6",
"electron-builder": "^20.38.5",
"typescript": "^3.3.3333",
"wait-on": "^3.2.0"
}
}

现在我们准备好打包应用。运行下面的命令:

1
yarn electron-pack

最终你会在 dist 目录看到打包结果

好了,该你上手实践了。

源码在 这里

希望这这篇博客对你有所帮助。 Bye!